-module(test_bed_master).
-export([all_slaves/2,default/0,loop/5]).
-export([set_func/4,run_tests/0]).
-export([start/0,start_all/0,start_all/1,x_slaves_one_node/1,start_slaves/1]).
-export([kill_all/0,slave_amount/0]).
-export([setf_db2/1,setf_mysql/1]).
-export([stop/0]).

%% Abstracted away message passing
set_func(Func,Times,Filename,Args) ->
   master ! {set_test,Func,Times,Filename,Args}.

%% Abstracted away message passing
run_tests() ->
   register(shell,self()),
   master ! {run_tests},
   receive
      {Fmt,Param} -> ok
   end,
   unregister(shell),
   {Fmt,Param}.

%% Abstracted away message passing
kill_all() ->
   master ! {kill_slaves}.

%% Simple set func
setf_db2(Args) ->
    set_func("test_db2",1,"testbed/ebin/test_db2.beam",Args).
setf_mysql(Args) ->
    set_func("test_mysql",1,"testbed/ebin/test_mysql.beam",Args).


%% test_bed_master:start_all().
%% test_bed_master:set_func(fun test_example:test_case_1/1,1,"test_example.beam").
%% test_bed_master:run_tests([]).

%% Launch the test bed master process!
start() ->
   Val = register(master,
                  spawn(test_bed_master,
                        loop,
                        [default,0,[],[],[]])),
   %% Unlinking so that we don't run into 
   %% problems with the connection between
   %% the terminal and the erlang node.
   %% (Just for safety)
   case Val of
      true -> unlink(whereis(master));
      false -> ok
   end.

stop() ->
    master ! {die}.

%% start slaves on all connected nodes
%start_all() ->
%   [spawn(Node, fun()->group_leader(whereis(user),self()), 
%                       test_bed_slave:init() 
%                end) 
%   || Node <- nodes()],
%   master ! {set_slaves,slaves()}.

%% start one slave on each connected node
start_all() ->
   Slaves = [spawn_on_node(1,N,[]) || N <- nodes()],
   master ! {set_slaves, lists:flatten(Slaves)}.   


%% start Amount of slaves on each connected node
start_all(Amount) ->
   Slaves = [spawn_on_node(Amount,N,[]) || N <- nodes()],
%   io:format("start_all(amount) Slaves is ~p~n",[Slaves]),
   master ! {set_slaves, lists:flatten(Slaves)}.


%% start TotalAmount of slaves on as many nodes as needed
start_slaves(TotalAmount) ->
   Nodes = get_random_nodes(TotalAmount,erlang:nodes()),
   AmountPerNode = TotalAmount div length(Nodes),
   Remaining = TotalAmount rem length(Nodes),
   Slaves = [spawn_on_node(AmountPerNode,N,[]) || N <- Nodes],
   case Remaining /= 0 of
      true -> 
         [N] = get_random_nodes(1,erlang:nodes()),
         RemSlaves = spawn_on_node(Remaining,N,[]),
         NewSlaves = Slaves ++ RemSlaves;
      false ->
         NewSlaves = Slaves
   end,
   master ! {set_slaves, lists:flatten(NewSlaves)}.


%% start Amount of slaves on one randomized node
x_slaves_one_node(Amount) ->
   Node = lists:nth(1,get_random_nodes(1,erlang:nodes())),
   Slaves = spawn_on_node(Amount,Node,[]),
   master ! {set_slaves, Slaves}.


%% helper function to spawn slaves on nodes,
%% used by all start functions above
spawn_on_node(0,_,StartedSlaves) ->
   StartedSlaves;
spawn_on_node(Amount,Node,StartedSlaves) ->
   S = spawn(Node, fun()->group_leader(whereis(user),self()), 
                          test_bed_slave:init() 
                   end),
   spawn_on_node(Amount-1,Node,StartedSlaves++[S]).

   


%start_amount(Amount) ->
%   Slaves = [spawn_one_slave() || _ <- lists:seq(1,Amount)],
%   master ! {set_slaves,Slaves}.

%spawn_one_slave() ->
%   Node = get_random_nodes(1,nodes()),
%   spawn(Node, fun()->group_leader(whereis(user),self()), 
%                       test_bed_slave:init() 
%                end). 
    

%% 2013-04-27
%% 
%% Now this function tells the main loop that
%% it the amount of slaves is actually not slaves()
%% but whatever this functions says.
start_with_args(Amount) ->
   Nodes = get_random_nodes(Amount,erlang:nodes()),
   io:fwrite("slaves: ~p",[Nodes]),
   [spawn(Node, fun()->group_leader(whereis(user),self())
         , test_bed_slave:init() end)
   || Node <- Nodes ],
   %% 
   FormattedNodes = lists:map(fun(X)->{slave,X} end,Nodes),
   master ! {set_slaves,FormattedNodes}.

get_random_nodes(Amount, Nodes) ->
    case Amount > length(Nodes) of 
        true  -> Nodes;
        false ->
            lists:foldl(fun(_, Acc) ->
                        [get_random_unique(Nodes, Acc)]
                            ++ Acc
                end, [], lists:seq(1, Amount))
    end.


get_random_unique(Nodes, CurrentList) ->
    <<A:32, B:32, C:32>> = crypto:strong_rand_bytes(12),
    random:seed(A,B,C),
    Node = lists:nth(random:uniform(length(Nodes)), Nodes),
    case lists:member(Node, CurrentList) of
        true  -> get_random_unique(Nodes, CurrentList);
        false -> Node
    end.
 
%% Kill all slaves on all connected nodes
%% Those who doesn't have nodes will not 
%% be affected.
%kill_all() ->
%   [
%      {slave,X} ! die  %%TODO ÄNDRA HÄR, SKICKA MEDDELANDE TILL LOOPEN SOM HÅLLER LISTAN master ! 
%      || X <- nodes()
%   ],
%   master ! {set_slaves,[]}.

slave_amount() ->
   {Res,_} = rpc:multicall(erlang:nodes(),erlang,whereis,[slave]),
   Filtered = [ R || R <- Res, R /= undefined ],
   io:fwrite("Amount of slaves currently running:"),
   io:format("~p",[length(Filtered)]).

%%
%% Main loop!
%% 
%% 2013-04-26: 
%% Added SlavesWorking to the main loop.
%% 
%% It is a list where every slave that is sent to do
%% a test is stored. They then report when they are
%% finished performing the test, and then they are 
%% removed from SlavesWorking. When SlavesWorking is
%% empty again, we know what all test instances have
%% run and we can notify the user of the master.

loop(Func, Times, Slaves, SlavesWorking, CollectedResults) ->
   receive
      %% Command to be called by a user, 
      %% will call slaves
      {set_test, Module, NewTimes, FileName, Args} ->
         case is_testcase(Module) of
            true ->
               io:fwrite("Master proc: sending new function to slaves\n"),
               %% Bin is the file loaded 
               Bin = read_file(FileName),
               io:format("This will fail. Slaves is ~p\n",[Slaves]),
               send_to_slaves({set_test,Module,Bin,FileName,Args},Slaves),

               loop(Module,NewTimes,Slaves,[],CollectedResults);
            false ->
               io:format("Master proc: Not a valid testcase: ~p~n",[Module]),
               loop(Func,Times,Slaves,[],CollectedResults)
         end;
      %% Command to be called by a user,
      %% will call slaves
      {run_tests} ->
         send_to_slaves({run_test,Times,os:timestamp(),self()},Slaves),
         %% Here, we add the list of slaves who are to perform work
         %% to the list of working slaves
         loop(Func,Times,Slaves,SlavesWorking++Slaves,CollectedResults);
      stats ->
         io:fwrite(lists:flatten(io_lib:format("~p",[length(Slaves)]))),
         io:fwrite("Master proc: sending stats to slaves\n"),
         send_to_slaves({stats,self()},Slaves),
         loop(Func,Times,Slaves,SlavesWorking,CollectedResults);
         
      %% Change 2013-04-26: From now on, the slaves have to
      %% submit themselves when finished.
      {run_result, Result, TimeElapsed, SlaveFinishedId} ->
         io:fwrite("length(SlavesWorking): ~p", [length(SlavesWorking)]),
         case (length(SlavesWorking) == 1) of
             %% This is when all working slaves are finished
             %% with whatever test they were running
             %% Loop again, this time without the working slave
             %% list and hte collected results
             
             %% CollectedResults :: [{Result,TimeElapsed}]
             %%     where Result itself is a list of results
         
             %% For now, print the collected results structure
             %% in a specialized function made for it
             true ->
                 print_test_results(CollectedResults++[{Result,TimeElapsed}]),
                 loop(Func,Times,Slaves,[],[]);
             %% Change 2013-04-26:
             %% removing the last
             false ->
                loop(Func,
                     Times,
                     Slaves,
                     SlavesWorking--[SlaveFinishedId],
                     CollectedResults++[{Result,TimeElapsed}])
         end;
         
      {stats_result, {_TotRuns, _TotQueryTimes}} ->
         io:fwrite("Master proc: Got stats yo\n"),
         loop(Func,Times,Slaves,SlavesWorking,CollectedResults);
      {set_slaves,NewSlaveList} ->
         %% Change 2013-04-27
         %% 
         %% NewSlaveList
         loop(Func,Times,NewSlaveList,[],CollectedResults);
      {kill_slaves} ->
         [S ! die || S <- Slaves],
         loop(Func,Times,[],[],[]);
      {die} ->
         unregister(master),
         ok;
      _ ->
         io:fwrite("Master proc: no match!"),
         loop(Func,Times,Slaves,SlavesWorking,CollectedResults)
   end.


%% Simplification to get all slaves
slaves() ->
   [{slave,X} || X <- erlang:nodes()].%, is_slave_name(atom_to_list(X))]. %%TODO ÄNDRA HÄR

%% Just a default 
default() ->
   io:fwrite("Master proc: hello?!\n").

%% Determine if a process is a slave
is_slave_name(Str) ->
   lists:sublist(Str,length("slnode")) == "slnode".

%% Find all slaves connected to the master
all_slaves([],SlavesLs) ->
   SlavesLs;
all_slaves([N|Ns],SlavesLs) ->
   Regs = rpc:call(N,erlang,registered,[]),
   Slaves = [{X,N} || X <- Regs, is_slave_name(atom_to_list(X))],
   all_slaves(Ns,SlavesLs++Slaves).

%% Send a message to all slaves the master knows of
send_to_slaves(_,[]) ->
   io:fwrite("Message sent to all slaves!\n");
send_to_slaves(Message,[X|Xs]) ->
  %% io:format("send_to_slaves, X is ~p\n",[X]),
   X ! Message,
   %%X ! Message,
   send_to_slaves(Message,Xs).

%% Read a file from disk and return it, or make it 
%% print that an exception happend.
read_file(FileName) ->
   case file:read_file(FileName) of
      {ok,Binary} -> Binary;
      {error,reason} ->
         io:fwrite("Error sending binary to test.."++reason)
   end.

%% Check if a module follows testcase behaviour
is_testcase(Mod) ->
    M = list_to_atom(Mod),
    lists:member({behaviour, [testcase]},M:module_info(attributes)).

% A toString function for writing Ints
to_string(Something) ->
   case length(Something) of
      1 -> io_lib:format("~p", Something);
      _ -> lists:flatten(io_lib:format("~p", [Something]))
   end.




%% Pretty printer of resultlist ([{Result,TimeElapsed}])
print_test_results(ResultList) ->
    {A1,B1} = print("\n---------------------------------------\n",[]),
    {A2,B2} = print("Received test results. Print statistics\n",[]),
    {A3,B3} = print("---------------------------------------\n\n",[]),
    {A4,B4} = print_slave_amount(ResultList),
    {A5,B5} = print_query_amount(ResultList),
    {A6,B6} = print_testbed_time(ResultList),
    {A7,B7} = print_testcase_time(ResultList),
    {A8,B8} = print_queries_per_second(ResultList),
 %   {A9,B9} = print_db_node_statistics(ResultList),
    {A10,B10} = print("\n---------------------------------------\n",[]),
    {A11,B11} = print("End of test statistics\n",[]),
    {A12,B12} = print("---------------------------------------\n",[]),
    shell ! {A1++A2++A3++A4++A5++A6++A7++A8++A10++A11++A12,
     B1++B2++B3++B4++B5++B6++B7++B8++B10++B11++B12}.


print_slave_amount(ResultList) ->
    print("Number of tests performed: ~w~n", [length(ResultList)]).

print_query_amount(ResultList) ->
    QueriesList = get_tuples(queries,ResultList),
    case length(QueriesList) > 0 of
        true ->
            Amount = lists:foldl(fun(A, Sum) -> 
                         Sum + A end, 0, QueriesList),
            print("Queries/slave (avg): ~w, ", 
                        [Amount div length(QueriesList)]),
            print("Queries (Total): ~w~n", [Amount]);
        false ->
            ok
    end.
print_testbed_time(ResultList) ->
    Time = get_timeelapsed(ResultList),
    Sum = lists:foldl(fun(T,Su) ->
                      T + Su end, 0, Time),
    print("Time Avg: ~w, Max: ~w~n",[Sum/length(Time),
                                           lists:max(Time)]).
print_testcase_time(ResultList) ->
    Time = get_tuples(time,ResultList),
    case length(Time) > 0 of
        true ->
            Sum = lists:foldl(fun(T,Su) ->
                      T + Su end, 0, Time),
            print("Time (from test)-- Avg: ~w, Max: ~w~n",
                    [Sum/length(Time), lists:max(Time)]);
        false ->
            ok
    end.

print_queries_per_second(ResultList) ->
    QueriesList = get_tuples(queries,ResultList),
    case length(QueriesList) > 0 of
        true ->
            Amount = lists:foldl(fun(A, Sum) -> 
                         Sum + A end, 0, QueriesList),
            Time = get_timeelapsed(ResultList),
            Sum = lists:foldl(fun(T,Su) ->
                      T + Su end, 0, Time),
            print("Queries/sec~n",[]),
            print("Time-- Avg: ~w, Max: ~w~n", 
                [Amount / (Sum / length(Time)), Amount / lists:max(Time)]);
        false -> ok
    end,
    Time2 = get_tuples(time,ResultList),
    case (length(Time2) > 0) and (length(QueriesList) > 0) of
        true ->
            Amount2 = lists:foldl(fun(A, Sum) -> 
                         Sum + A end, 0, QueriesList),
            Sum2 = lists:foldl(fun(T,Su) ->
                               T + Su end, 0, Time2),
            print("TestTime-- Avg: ~w, Max: ~w~n", 
                [Amount2 / (Sum2 / length(Time2)), Amount2 / lists:max(Time2)]);
        false ->
            ok
    end.
    
print_db_node_statistics(_ResultList) ->
    ok.

%% Print function, possible to write to file
%% UPDATE: print returns the results (for testbot)
print(Fmt, Params) ->
    {Fmt, Params}.
    %{ok,Dev} = file:open("results.txt",[append]),
    %file:write(Dev,io_lib:format(Fmt,Params)),
    %file:close(Dev).


get_timeelapsed(ResultList) ->
    [Time || {_,Time} <- ResultList].
get_results(ResultList) ->
    [Result || {Result,_} <- ResultList].
get_tuples(Type, ResultList) ->
    [A || {T,A } <- lists:flatten(get_results(ResultList)), T == Type].
    
